package com.xiaoyushu.user.biz.service.impl;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.xiaoyushu.framework.biz.context.holder.LoginUserContextHolder;
import com.xiaoyushu.framework.common.enums.DeletedEnum;
import com.xiaoyushu.framework.common.enums.StatusEnum;
import com.xiaoyushu.framework.common.exception.BizException;
import com.xiaoyushu.framework.common.response.Response;
import com.xiaoyushu.framework.common.util.JsonUtils;
import com.xiaoyushu.framework.common.util.ParamUtils;
import com.xiaoyushu.user.biz.constant.RedisKeyConstants;
import com.xiaoyushu.user.biz.constant.RoleConstants;
import com.xiaoyushu.user.biz.domain.dataobject.RoleDO;
import com.xiaoyushu.user.biz.domain.dataobject.UserDO;
import com.xiaoyushu.user.biz.domain.dataobject.UserRoleDO;
import com.xiaoyushu.user.biz.domain.mapper.RoleDOMapper;
import com.xiaoyushu.user.biz.domain.mapper.UserDOMapper;
import com.xiaoyushu.user.biz.domain.mapper.UserRoleDOMapper;
import com.xiaoyushu.user.biz.enums.ResponseCodeEnum;
import com.xiaoyushu.user.biz.enums.SexEnum;
import com.xiaoyushu.user.biz.model.vo.UpdateUserInfoReqVO;
import com.xiaoyushu.user.biz.rpc.DistributedIdGeneratorRpcService;
import com.xiaoyushu.user.biz.rpc.OssRpcService;
import com.xiaoyushu.user.biz.service.UserService;
import com.xiaoyushu.user.dto.req.*;
import com.xiaoyushu.user.dto.resp.FindUserByIdRspDTO;
import com.xiaoyushu.user.dto.resp.FindUserByPhoneRspDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Resource
    private UserDOMapper userDOMapper;


    @Autowired
    private OssRpcService ossRpcService;

    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private UserRoleDOMapper userRoleDOMapper;
    @Resource
    private RoleDOMapper roleDOMapper;

    @Resource(name = "taskExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;


    @Resource
    private DistributedIdGeneratorRpcService distributedIdGeneratorRpcService;

    /**
     * 用户信息本地 缓存
     */
    private static final Cache<Long, FindUserByIdRspDTO> LOCAL_CACHE=  Caffeine.newBuilder()
            .initialCapacity(10000) // 设置初始容量为 10000 个条目
            .maximumSize(10000) // 设置缓存的最大容量为 10000 个条目
            .expireAfterWrite(1, TimeUnit.HOURS) // 设置缓存条目在写入后 1 小时过期
            .build();


    /**
     * 更新用户信息
     *
     * @param updateUserInfoReqVO
     * @return
     */
    @Override
    public Response<?> updateUserInfo(UpdateUserInfoReqVO updateUserInfoReqVO) {
        UserDO userDO = new UserDO();
        // 设置当前需要更新的用户 ID
        userDO.setId(LoginUserContextHolder.getUserId());
        // 标识位：是否需要更新
        boolean needUpdate = false;
        
        // 头像
        MultipartFile avatarFile = updateUserInfoReqVO.getAvatar();

        if (Objects.nonNull(avatarFile)) {
            //调用 openfeign
            String avatar = ossRpcService.uploadFile(avatarFile);
            log.info("==> 调用 oss服务成功，上传头像，url:{}",avatar);
            //若上传头像失败，则抛出业务异常
            if (StringUtils.isBlank(avatar)){
                throw new BizException(ResponseCodeEnum.UPLOAD_AVATAT_FAIL);
            }
            userDO.setAvatar(avatar);
            needUpdate = true;
        }

        // 昵称
        String nickname = updateUserInfoReqVO.getNickname();
        if (StringUtils.isNotBlank(nickname)) {
            Preconditions.checkArgument(ParamUtils.checkNickname(nickname), ResponseCodeEnum.NICK_NAME_VALID_FAIL.getErrorMessage());
            userDO.setNickname(nickname);
            needUpdate = true;
        }

        // 小哈书号
        String xiaohashuId = updateUserInfoReqVO.getXiaohashuId();
        if (StringUtils.isNotBlank(xiaohashuId)) {
            Preconditions.checkArgument(ParamUtils.checkXiaohashuId(xiaohashuId), ResponseCodeEnum.XIAOHASHU_ID_VALID_FAIL.getErrorMessage());
            userDO.setXiaohashuId(xiaohashuId);
            needUpdate = true;
        }

        // 性别
        Integer sex = updateUserInfoReqVO.getSex();
        if (Objects.nonNull(sex)) {
            Preconditions.checkArgument(SexEnum.isValid(sex), ResponseCodeEnum.SEX_VALID_FAIL.getErrorMessage());
            userDO.setSex(sex);
            needUpdate = true;
        }

        // 生日
        LocalDate birthday = updateUserInfoReqVO.getBirthday();
        if (Objects.nonNull(birthday)) {
            userDO.setBirthday(birthday);
            needUpdate = true;
        }

        // 个人简介
        String introduction = updateUserInfoReqVO.getIntroduction();
        if (StringUtils.isNotBlank(introduction)) {
            Preconditions.checkArgument(ParamUtils.checkLength(introduction, 100), ResponseCodeEnum.INTRODUCTION_VALID_FAIL.getErrorMessage());
            userDO.setIntroduction(introduction);
            needUpdate = true;
        }

        // 背景图
        MultipartFile backgroundImgFile = updateUserInfoReqVO.getBackgroundImg();
        if (Objects.nonNull(backgroundImgFile)) {
            // todo: 调用对象存储服务上传文件
            String backgroundImg = ossRpcService.uploadFile(backgroundImgFile);
            //若上传头像失败，则抛出业务异常
            if (StringUtils.isBlank(backgroundImg)){
                throw new BizException(ResponseCodeEnum.UPLOAD_AVATAT_FAIL);
            }
            userDO.setBackgroundImg(backgroundImg);
            needUpdate = true;
        }

        if (needUpdate) {
            // 更新用户信息
            userDO.setUpdateTime(LocalDateTime.now());
            userDOMapper.updateByPrimaryKeySelective(userDO);
        }
        return Response.success();
    }

    /**
     * 用户注册
     *
     * @param registerUserReqDTO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response<Long> register(RegisterUserReqDTO registerUserReqDTO) {
      String phone=  registerUserReqDTO.getPhone();
      //先判断 该手机号是否已经注册
       UserDO userDO1 = userDOMapper.selectByPhone( phone);
        log.info("==> 用户是否注册, phone: {}, userDO: {}", phone, JsonUtils.toJsonString(userDO1));

        // 若已注册，则直接返回用户 ID
        if (Objects.nonNull(userDO1)) {
            return Response.success(userDO1.getId());
        }

        // 否则注册新用户
        // 获取全局自增的小哈书 ID
       // Long xiaohashuId = redisTemplate.opsForValue().increment(RedisKeyConstants.XIAOHASHU_ID_GENERATOR_KEY);
//RPC：调用分布式 ID 生成服务生成小哈书ID
        String xiaohashuId = distributedIdGeneratorRpcService.getXioayushuId();
        String userIdStr = distributedIdGeneratorRpcService.getUserId();
        Long userId=Long.valueOf(userIdStr);

        UserDO userDO = UserDO.builder()
                .phone(phone)
                .xiaohashuId(String.valueOf(xiaohashuId)) // 自动生成小红书号 ID
                .nickname("小红薯" + xiaohashuId) // 自动生成昵称, 如：小红薯10000
                .status(StatusEnum.ENABLE.getValue()) // 状态为启用
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .isDeleted(DeletedEnum.NO.getValue()) // 逻辑删除
                .build();

        // 添加入库
        userDOMapper.insert(userDO);

        // 获取刚刚添加入库的用户 ID
      //  Long userId = userDO.getId();

        // 给该用户分配一个默认角色
        UserRoleDO userRoleDO = UserRoleDO.builder()
                .userId(userId)
                .roleId(RoleConstants.COMMON_USER_ROLE_ID)
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .isDeleted(DeletedEnum.NO.getValue())
                .build();
        userRoleDOMapper.insert(userRoleDO);

        RoleDO roleDO = roleDOMapper.selectByPrimaryKey(RoleConstants.COMMON_USER_ROLE_ID);

        // 将该用户的角色 ID 存入 Redis 中
        List<String> roles = new ArrayList<>(1);
        roles.add(roleDO.getRoleKey());

        String userRolesKey = RedisKeyConstants.buildUserRoleKey(userId);
        redisTemplate.opsForValue().set(userRolesKey, JsonUtils.toJsonString(roles));

        return Response.success(userId);

    }

    /**
     * @param findUserByPhoneReqDTO
     * @return
     */
    @Override
    public Response<FindUserByPhoneRspDTO> findByPhone(FindUserByPhoneReqDTO findUserByPhoneReqDTO) {
        String phone = findUserByPhoneReqDTO.getPhone();
        // 根据手机号拆线呢用户信息
        UserDO userDO = userDOMapper.selectByPhone(phone);
        //判空
        if (Objects.isNull(userDO)) {
            throw new BizException(ResponseCodeEnum.USER_NOT_FOUND);
        }
        //构建返回参数
        FindUserByPhoneRspDTO build = FindUserByPhoneRspDTO.builder()
                .id(userDO.getId())
                .password(userDO.getPassword())
                .build();
        return Response.success(build);
    }

    /**
     * 更新密码
     *
     * @param updateUserPasswordReqDTO
     */
    @Override
    public Response<?> updatePassword(UpdateUserPasswordReqDTO updateUserPasswordReqDTO) {
       //获取当前请求对应的用户ID
       Long userId= LoginUserContextHolder.getUserId();
       UserDO userDO= UserDO.builder()
                .id(userId)
                .password(updateUserPasswordReqDTO.getEncodePassword())
                .updateTime(LocalDateTime.now())
                .build();

        //更新密码
        userDOMapper.updateByPrimaryKeySelective(userDO);

        return Response.success();
    }

    /**
     * @param findUserByIdReqDTO
     * @return
     */
    @Override
    public Response<FindUserByIdRspDTO> findById(FindUserByIdReqDTO findUserByIdReqDTO) {
       Long userId= findUserByIdReqDTO.getId();

       //先从缓存中查询
        FindUserByIdRspDTO ifPresent = LOCAL_CACHE.getIfPresent(userId);
        if (Objects.nonNull(ifPresent)) {
            log.info("==>命中了本地缓存；{}", ifPresent);
            return Response.success(ifPresent);
        }


        // 用户缓存 Redis key
        String userInfoRedisKey = RedisKeyConstants.buildUserInfoKey(userId);
        // 先从 Redis 缓存中查询
        String userInfoRedisValue = (String) redisTemplate.opsForValue().get(userInfoRedisKey);

        // 若 Redis 缓存中存在该用户信息
        if (StringUtils.isNotBlank(userInfoRedisValue)) {
            // 将存储的 Json 字符串转换成对象，并返回
            FindUserByIdRspDTO findUserByIdRspDTO = JsonUtils.parseObject(userInfoRedisValue, FindUserByIdRspDTO.class);
            //异步线程中 将用户信息存入本地缓存
            threadPoolTaskExecutor.submit(() -> {
                if (Objects.nonNull(findUserByIdRspDTO)){
                    //写入本地缓存
                    LOCAL_CACHE.put(userId, findUserByIdRspDTO);
                }
            });
            return Response.success(findUserByIdRspDTO);
        }

        // 否则, 从数据库中查询
        // 根据用户 ID 查询用户信息
        UserDO userDO = userDOMapper.selectByPrimaryKey(userId);

        // 判空
        if (Objects.isNull(userDO)) {
            threadPoolTaskExecutor.execute(() -> {
                // 防止缓存穿透，将空数据存入 Redis 缓存 (过期时间不宜设置过长)
                // 保底1分钟 + 随机秒数
                long expireSeconds = 60 + RandomUtil.randomInt(60);
                redisTemplate.opsForValue().set(userInfoRedisKey, "null", expireSeconds, TimeUnit.SECONDS);
            });
            throw new BizException(ResponseCodeEnum.USER_NOT_FOUND);
        }

        // 构建返参
        FindUserByIdRspDTO findUserByIdRspDTO = FindUserByIdRspDTO.builder()
                .id(userDO.getId())
                .nickName(userDO.getNickname())
                .avatar(userDO.getAvatar())
                .introduction(userDO.getIntroduction())
                .build();

        // 异步将用户信息存入 Redis 缓存，提升响应速度
        threadPoolTaskExecutor.submit(() -> {
            // 过期时间（保底1天 + 随机秒数，将缓存过期时间打散，防止同一时间大量缓存失效，导致数据库压力太大）
            long expireSeconds = 60*60*24 + RandomUtil.randomInt(60*60*24);
            redisTemplate.opsForValue()
                    .set(userInfoRedisKey, JsonUtils.toJsonString(findUserByIdRspDTO), expireSeconds, TimeUnit.SECONDS);
        });

        return Response.success(findUserByIdRspDTO);
    }

    /**
     * @param findUsersByIdsReqDTO
     * @return
     */
    @Override
    public Response<List<FindUserByIdRspDTO>> findByIds(FindUsersByIdsReqDTO findUsersByIdsReqDTO) {
        //需要查询的用户 ID 集合
        List<Long> userIds = findUsersByIdsReqDTO.getIds();

        //构建Redis Key 集合
        List<String> redisKeys = userIds.stream()
                .map(RedisKeyConstants::buildUserInfoKey)
                .toList();

        //先从 Redis 缓存中查,multiGet 批量批量呢提升性能
        List<Object> redisValues=redisTemplate.opsForValue().multiGet(redisKeys);
        //如果缓存中不为 空
        if (CollUtil.isNotEmpty(redisValues)){
            //过滤掉为空的数据
           redisValues= redisValues.stream().filter(Objects::nonNull).toList();
        }
        //反参
        List<FindUserByIdRspDTO> findUserByIdRspDTOS=Lists.newArrayList();

        //将过滤后的缓存集合,转换为 DTO 反参实体类
        if (CollUtil.isNotEmpty(redisValues)){
            findUserByIdRspDTOS=redisValues.stream()
                    .map(value->JsonUtils.parseObject(String.valueOf(value),FindUserByIdRspDTO.class))
                    .collect(Collectors.toList());
        }

        //如果被查询的用户信息,都在Redis缓存中,则直接返回
        if (CollUtil.size(userIds) == CollUtil.size(findUserByIdRspDTOS)){
            return Response.success(findUserByIdRspDTOS);
        }

        //还有另外两种情况:一种是缓存里没有用户信息数据,还有一种是缓存数据不全,需要从数据库中补充
        //筛选出缓存里没有的用户数据,去查数据库
        List<Long> userIdsNeedQuery =null;
        if (CollUtil.isNotEmpty(findUserByIdRspDTOS)){
            //去重后,数量不等,且不是空,重新把id拿到
            //将 findUserByIdRspDTOS 集合转 Map
         Map<Long ,FindUserByIdRspDTO> map= findUserByIdRspDTOS.stream()
                    .collect(Collectors.toMap(FindUserByIdRspDTO::getId,p->p));
            //筛选出需要查 DB 的用户 ID
            userIdsNeedQuery=userIds.stream()
                    .filter(id->Objects.isNull(map.get(id)))
                    .toList();
        }else{  //缓存中一条用户信息都没查到,则刚提交的用户 ID 集合都需要查数据库
            userIdsNeedQuery=userIds;
        }

        //从数据库中批量查询
        List<UserDO> userDOS=userDOMapper.selectByIds(userIdsNeedQuery);

        List<FindUserByIdRspDTO> findUserByIdRspDTOS2=null;

        //若数据库查询的记录不为空
        if(CollUtil.isNotEmpty(userDOS)){
            //DO转 DTO
            findUserByIdRspDTOS2=userDOS.stream()
                    .map(userDO -> FindUserByIdRspDTO.builder()
                            .id(userDO.getId())
                            .nickName(userDO.getNickname())
                            .avatar(userDO.getAvatar())
                            .introduction(userDO.getIntroduction())
                            .build()
                    ).collect(Collectors.toList());
             //异步线程:使用pipeline管道,同步数据到Redis
            // 异步线程将用户信息同步到Redis中
            List<FindUserByIdRspDTO> finalFindUserByIdRspDTO=findUserByIdRspDTOS2;
            threadPoolTaskExecutor.submit(()->{
                //DTO 集合转 Map
               Map<Long,FindUserByIdRspDTO> map= finalFindUserByIdRspDTO.stream()
                        .collect(Collectors.toMap(FindUserByIdRspDTO::getId,p->p));
                //执行 pipeline 操作
               redisTemplate.executePipelined(new SessionCallback<>() {

                   /**
                    * @param operations
                    * @param <K>
                    * @param <V>
                    * @return
                    * @throws DataAccessException
                    */
                   @Override
                   public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
                      for (UserDO userDO:userDOS){
                          Long userId=userDO.getId();

                          //用户信息缓存 Redis Key
                         String userInfoRedisKey= RedisKeyConstants.buildUserInfoKey(userDO.getId());
                          //DTO 转 JSON字符串
                          FindUserByIdRspDTO findUserByIdRspDTO=map.get(userId);
                          String value =JsonUtils.toJsonString(findUserByIdRspDTO);
                          //过期时间(保底1天 + 随机秒数,将缓存过期时间打散，防止同一时间大量缓存失效，导致数据库压力太大）
                          long expireSeconds=60*60*24 +RandomUtil.randomInt(60*60+24);
                            operations.opsForValue().set((K) userInfoRedisKey, (V) value,expireSeconds,TimeUnit.SECONDS);
                      }

                       return null;
                   }
               });
            });

        }
        //合并数据
        if (CollUtil.isNotEmpty(findUserByIdRspDTOS2)){
            findUserByIdRspDTOS.addAll(findUserByIdRspDTOS2);
        }

        return Response.success(findUserByIdRspDTOS);

    }
}
